#!/usr/bin/env python3

import json
import networkx as nx
# from delay_detector import DelayDetector
import delay_detector
import simple_switch_13
import shortest_path_forward
import path_forward

from ryu.topology import event, switches
from gui_topology.gui_topology import GUIServerApp
from ofctl_rest import StatsController
from rest_api import RestStatsApiExt
from ryu.ofproto import ofproto_v1_3
from ryu.ofproto import ofproto_v1_0
from ryu.base import app_manager
from ryu.app.wsgi import ControllerBase, WSGIApplication, route, Response
from ryu.controller import dpset


class RestAppCtl(app_manager.RyuApp):
    # OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION, ofproto_v1_3.OFP_VERSION]
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
    _CONTEXTS = {
        'dpset': dpset.DPSet,
        'wsgi': WSGIApplication,
        'switches': switches.Switches
    }

    def __init__(self, *args, **kwargs):
        super(RestAppCtl, self).__init__(*args, **kwargs)
        self.dpset = kwargs['dpset']
        wsgi = kwargs['wsgi']
        switches = kwargs['switches']

        # self.data will be passed to controller
        self.data = {}

        wsgi.register(PathController)


class PathController(ControllerBase):
    def __init__(self, req, link, data, **config):
        super(PathController, self).__init__(req, link, data, **config)
        self.ryu_mgr = app_manager.AppManager.get_instance()


    @route('quality', '/load/quality', methods=['POST'])
    def load_quality(self, req, **kwargs):
        app_mgr = app_manager.AppManager.get_instance()
        print(app_mgr.applications_cls.items())

        # clean the apps
        app_mgr.close()
        # app_mgr.applications_cls.pop('shortest_path_forward.py')
        # app_mgr.applications_cls.pop('ryu.topology.switches')
        print(app_mgr.applications_cls.items())
        app = app_mgr.applications.get('shortest_path_forward')
        if app is not None:
            print(f'Stopping application: "shortest_path_forward"')
            app.stop()
        # app_mgr.uninstantiate('shortest_path_forward')
        app_list = ['gui_topology.gui_topology', 'rest_api', 'delay_detector']
        # gui_topology/gui_topology.py rest_api.py shortest_path_forward.py
        app_mgr.run_apps(app_list)
        print(f"\nApplication has been started.\n")
        return Response("Applications have been restarted successfully.", status=200, mimetype='text/plain')


    @route('hop', '/load/hop', methods=['POST'])
    def load_hop(self, req, **kwargs):
        app_mgr = app_manager.AppManager.get_instance()

        # for app_name in list(app_mgr.applications.keys()):
        #     app = app_mgr.applications.get(app_name)
        #     if app is not None:
        #         print(f'Stopping application: {app_name}')
        #         app.stop()
        # clean the apps
        print(app_mgr.applications_cls.items())

        # clean the apps
        app_mgr.close()
        # app_mgr.applications_cls.pop('delay_detector')
        # print(app_mgr.applications_cls.items())
        # TODO: Not working
        app = app_mgr.applications.get('delay_detector')
        if app is not None:
            print(f'Stopping application: "delay_detector"')
            app.stop()

        # app = app_mgr.applications.get('switches')
        # if app is not None:
        #     print(f'Stopping application: "delay_detector"')
        #     app.stop()
        # app = app_mgr.applications.get('ryu.topology.switches')
        # if app is not None:
        #     app.stop()
        # app_mgr.uninstantiate('ryu.topology.switches')
        app_list = ['gui_topology.gui_topology', 'shortest_path_forward', 'rest_api', ]
        app_mgr.run_apps(app_list)

        print(f"\nApplication has been started.\n")
        return Response("Applications have been restarted successfully.", status=200, mimetype='text/plain')

    # @route('{app_name}', '/unload/{app_name}', methods=['POST'], requirements={'app_name': r'\w+'})
    # def stop_app(self, req, **kwargs):
    #     app_name = kwargs.get('app_name')
    #     app_mgr = app_manager.AppManager.get_instance()
    #     app = app_mgr.applications.get(app_name)
    #     if app is not None:
    #         app.stop()
    #         print(f"\nApplication {app_name} has been stopped.\n")
    #     else:
    #         print(f"\nApplication {app_name} not found.\n")

    # @route('{app_name}', '/load/{app_name}', methods=['POST'], requirements={'app_name': r'\w+'})
    # def start_app(self, req, **kwargs):
    #     app_name = kwargs.get('app_name')
    #     app_mgr = app_manager.AppManager.get_instance()
    #     app = app_mgr.applications.get(app_name)
    #     if app is not None:
    #         app.start()
    #         print(f"\nApplication {app_name} has been started.\n")
    #     else:
    #         print(f"\nApplication {app_name} not found.\n")

    # @route('{app_name}', '/load/{app_name}', methods=['POST'], requirements={'app_name': r'\w+'})
    # def load_app(self, req, **kwargs):
    #     app_name = kwargs['app_name']
    #     app_manager.load_app(app_name)
    #     app_manager.instantiate_apps()

    # @route('{app_name}', '/unload/{app_name}', methods=['POST'], requirements={'app_name': r'\w+'})
    # def unload_app(self, req, **kwargs):
    #     app_name = kwargs['app_name']
    #     app_manager.unload_app(app_name)

    @route('list', '/app/list', methods=['GET'])
    def list_apps(self, req):
        app_mgr = app_manager.AppManager.get_instance()
        # Collect the application names
        app_names = [app for app in app_mgr.applications]
        # Create a JSON response
        body = json.dumps({"applications": app_names})
        # Return the response
        return Response(content_type='application/json', body=body)

    # @route('install', '/test/install', methods=['POST'])
    # def load_test(self, req, **kwargs):
    #     path = 'delay_detector'
    #     self.install_app(path)
    #     success_message = {'status': 'success', 'message': 'Operation completed successfully.'}
    #     return Response(content_type='application/json; charset=utf-8',
    #                     body=json.dumps(success_message).encode('utf-8'))

    # @route('installh', '/test/installh', methods=['POST'])
    # def load_testh(self, req, **kwargs):
    #     path = 'simple_switch_13'
    #     self.install_app(path)
    #     success_message = {'status': 'success', 'message': 'Operation completed successfully.'}
    #     return Response(content_type='application/json; charset=utf-8',
    #                     body=json.dumps(success_message).encode('utf-8'))

    @route('{app_name}', '/app/install/{app_name}', methods=['POST'], requirements={'app_name': r'\w+'})
    def load_app(self, req, **kwargs):
        path = kwargs['app_name']
        self.install_app(path)
        success_message = {'status': 'success', 'message': 'Operation completed successfully.'}
        return Response(content_type='application/json; charset=utf-8',
                        body=json.dumps(success_message).encode('utf-8'))

    @route('{app_name}', '/app/uninstall/{app_name}', methods=['POST'], requirements={'app_name': r'\w+'})
    def unload_app(self, req, **kwargs):
        path = kwargs['app_name']
        self.uninstall_app(path)
        success_message = {'status': 'success', 'message': 'Operation completed successfully.'}
        return Response(content_type='application/json; charset=utf-8',
                        body=json.dumps(success_message).encode('utf-8'))

    # @route('uninstall', '/app/uninstall', methods=['POST'])
    # def unload_test(self, req, **kwargs):
    #     path = 'delay_detector'
    #     self.uninstall_app(path)
    #     success_message = {'status': 'success', 'message': 'Operation completed successfully.'}
    #     return Response(content_type='application/json; charset=utf-8',
    #                     body=json.dumps(success_message).encode('utf-8'))

    def uninstall_app(self, path):
        app_cls = self.ryu_mgr.load_app(path)
        app = None

        for _app in self.ryu_mgr.applications.values():
            if isinstance(_app, app_cls):
                app = _app
                break

        else:
            raise ValueError('Can\'t find application')

        print('print app', app)
        self.ryu_mgr.uninstantiate(app.name)
        if hasattr(app, '__del__'):
            app.__del__()

        # after we stoped application, chack it context
        app_contexts = app_cls._CONTEXTS
        installed_apps = self.ryu_mgr.applications
        installed_apps_cls =\
            [obj.__class__ for obj in installed_apps.values()]

        ctx_to_keep = []
        for app_cls in installed_apps_cls:
            ctx_to_keep.extend(app_cls._CONTEXTS.keys())
        print(f'cls{app_contexts}\n keep{ctx_to_keep}')

        # if ctx.name in self.ryu_mgr.applications:
        #     del self.ryu_mgr.applications[ctx.name]

        for ctx_name in [_ for _ in app_contexts if _ not in ctx_to_keep]:
            # remove this context
            print(f'\n{ ctx_name }\n', )
            ctx_cls = app_contexts[ctx_name]
            ctx = self.ryu_mgr.contexts[ctx_name]
            print(f'ctx\n{ ctx }\n', )
            if issubclass(ctx_cls, app_manager.RyuApp):
                ctx.stop()

                if ctx.name in self.ryu_mgr.applications:
                    del self.ryu_mgr.applications[ctx.name]

                if ctx.name in app_manager.SERVICE_BRICKS:
                    del app_manager.SERVICE_BRICKS[ctx.name]

                print(f'Uninstall app {ctx.name} successfully')

            if ctx_name in self.ryu_mgr.contexts:
                del self.ryu_mgr.contexts[ctx_name]

            print(f'ctx after del {self.ryu_mgr.contexts}')

        print(f'\n{self.ryu_mgr.applications}\n')

            # if ctx.name in app_manager.SERVICE_BRICKS:
            #     del app_manager.SERVICE_BRICKS[ctx.name]

            # ctx.logger.info('Uninstall app %s successfully', ctx.name)

            # # handler hacking, remove all stream handler to avoid it log many times!
            # ctx.logger.handlers = []

    def install_app(self, path):
        context_modules = [x.__module__ for x in self.ryu_mgr.contexts_cls.values()]

        print(f'\n{self.ryu_mgr.applications}\n')
        if path in context_modules:
            ex = ValueError('Application already exist in contexts')
            raise ex

        print('loading app %s', path)
        app_cls = self.ryu_mgr.load_app(path)

        if app_cls is None:
            ex = ValueError('Can\'t find application module')
            raise ex

        self._install_app(app_cls)

        print(f'\n{self.ryu_mgr.applications}\n')

    def _install_app(self, app_cls):
        app_contexts = app_cls._CONTEXTS
        installed_apps = self.ryu_mgr.applications
        installed_apps_cls =\
            [obj.__class__ for obj in installed_apps.values()]

        if app_cls in installed_apps_cls:
            # app was installed
            print('Application already installed')
            ex = ValueError('Application already installed')
            raise ex

        new_contexts = []

        for k in app_contexts:
            context_cls = app_contexts[k]
            ctx = self.create_context(k, context_cls)

            if ctx and issubclass(context_cls, app_manager.RyuApp):
                new_contexts.append(ctx)

        app = self.ryu_mgr.instantiate(app_cls, **self.ryu_mgr.contexts)
        print(f'\n app{app}\n')
        # self.ryu_mgr.run_apps(app)
        # app.start()
        new_contexts.append(app)

        for ctx in new_contexts:
            t = ctx.start()

    def create_context(self, key, cls):
        context = None
        print(cls)

        if issubclass(cls, app_manager.RyuApp):
            context = self.ryu_mgr._instantiate(None, cls)
        else:
            context = cls()

        print(f'creating context {key}', )

        if key in self.ryu_mgr.contexts:
            return None

        self.ryu_mgr.contexts.setdefault(key, context)
        return context
